/**
 * License would add here
 */

#ifndef UTILS_JS_VALUE_WRAPPER_H
#define UTILS_JS_VALUE_WRAPPER_H

#ifndef OHOS_PLATFORM
#include <node_api.h>
#else
#include <napi/native_api.h>
#endif

#include "js_value_base.h"
#include "js_value_ref.h"
#include "macros.h"

typedef napi_finalize Finalizer;

class JSValueWrapper {
public:
    JSValueWrapper(const napi_env& env,
        const napi_value& value,
        void* data,
        Finalizer finalizeCB,
        void* hint = nullptr)
        : env_(env)
        , ref_(env)
        , callback_(finalizeCB)
        , data_(data)
    {
        JSValue obj(env, value);
        if (!obj.IsObject() && !obj.IsFunction()) {
            THROW_EXCEPTION("object expected");
        }
        ref_ = CreateWrapper(env_, value, this, CallbackTrigger, hint);
    }
    JSValueWrapper(const JSValue& value,
        void* data,
        Finalizer finalizeCB,
        void* hint = nullptr)
        : env_(value.env_)
        , ref_(value.env_)
        , callback_(finalizeCB)
        , data_(data)
    {
        if (!value.IsObject() && !value.IsFunction()) {
            THROW_EXCEPTION("object expected");
        }
        ref_ = CreateWrapper(env_, value, this, CallbackTrigger, hint);
    }

    ~JSValueWrapper()
    {
        napi_remove_env_cleanup_hook(env_, CleanUp, this);
        JSValueWrapper* that = nullptr;
        callback_ = nullptr;
        NAPI_STATUS_CALL(env_, napi_remove_wrap(env_, ref_, reinterpret_cast<void**>(&that)));
        ref_ = nullptr;
    }

    inline JSValue GetValue() const
    {
        JSValue result(env_);
        napi_get_reference_value(env_, ref_, &result);
        result.UpdateType();
        return result;
    }

    inline void* GetData() const
    {
        return data_;
    }

    inline operator napi_value() const { return GetValue(); }
    inline operator JSValue() const { return JSValue(env_, GetValue()); }

    inline JSValue ToJSValue() { return JSValue(env_, GetValue()); }

    inline static napi_ref CreateWrapper(napi_env env,
        napi_value value,
        void* data,
        Finalizer finalizeCB,
        void* hint)
    {
        napi_ref ref = nullptr;
        NAPI_STATUS_CALL(env, napi_wrap(env, value, data, finalizeCB, hint, &ref));
#ifdef OHOS_PLATFORM
        uint32_t refCount = 0;
        NAPI_STATUS_CALL(env, napi_reference_unref(env, ref, &refCount));
#endif

        return ref;
    }

private:
    static void CallbackTrigger(napi_env env, void* data, void* hint)
    {
        auto that = reinterpret_cast<JSValueWrapper*>(data);
        if (that->callback_ != nullptr) {
            that->callback_(env, that->data_, hint);
        }
    }
    static void CleanUp(void* data)
    {
        delete reinterpret_cast<JSValueWrapper*>(data);
    }

    void RegistCleanUpHook()
    {
        napi_add_env_cleanup_hook(env_, CleanUp, this);
    }

    const napi_env env_ { nullptr };
    JSValueRef ref_;

    Finalizer callback_ { nullptr };
    void* data_ { nullptr };
};

#endif /* UTILS_JS_VALUE_WRAPPER_H */